/*******************************************************************************
 * This file is part of blueprint4j www.blueprint4j.org.
 * 
 *     blueprint4j is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     blueprint4j is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with blueprint4j. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package blueprint4j.db;

import java.sql.SQLException;

import blueprint4j.utils.*;

import java.util.Vector;
import java.io.File;

public class ConnectionPool implements ThreadScheduable {
	 public static final int DBTYPE_MYSQL = 0;
	 public static final int DBTYPE_HSSQL_EMBED = 1;
	 public static final int DBTYPE_HSSQL_NETWORK = 2;
	 public static final int DBTYPE_MSSQL = 3;
	 public static final int DBTYPE_INFMX = 4;
	 public static final int DBTYPE_FIREB = 5;
	 public static final int DBTYPE_DERBY_EMBED = 6;
	 public static final int DBTYPE_DERBY_NETWORK = 7;

	 private String classForName = null;
	 private String url = null;
	 private String path = null;
	 private String dbname = null;
	 private String host = null;
	 private String username = null;
	 private String password = null;

	 private VectorDBConStruct pool = new VectorDBConStruct();
	 private static int POOLSIZE = Settings.getInt("database.pool.size",10);

	 /**
	  * public ConnectionPool(String p_classForName,String p_url,String p_username,String p_password)
	  * throws DataException,SQLException
	  *
	  * Constructs a Connection Pool Builder using its driver details, url and password
	  */
	 public ConnectionPool( int dbtype, String p_host,String p_dbname, String p_path,String p_username, String p_password)
	 throws DataException, SQLException {
		  username = p_username;
		  password = p_password;
		  dbname = p_dbname;
		  host = p_host;
		  path = p_path;
		  if (path != null && path.length()> 0) {
				path = new File(path).getAbsolutePath();
		  }
		  switch (dbtype) {
				case DBTYPE_MYSQL: {
					 /*dags mysql upgrade classForName = "com.mysql.jdbc.Driver";
					  * url = "jdbc:mysql://"+host+"/"+dbname+"?autoReconnect=true";
					  */
					 classForName = "org.gjt.mm.mysql.Driver";
					 url = "jdbc:mysql://"+host+"/"+dbname;
					 break;
				}
				case DBTYPE_HSSQL_EMBED: {
					 if (path == null) {
						  path = DataBaseUtilsHSQL.DEFAULT_DB_LOCATION_RELATIVE;
					 }
					 classForName = "org.hsqldb.jdbcDriver";
					 url = "jdbc:hsqldb:file:"+path+"/"+dbname;
					 break;
				}
				case DBTYPE_HSSQL_NETWORK: {
					 if (path == null) {
						  path = DataBaseUtilsHSQL.DEFAULT_DB_LOCATION_RELATIVE;
					 }
					 classForName = "org.hsqldb.jdbcDriver";
					 if (host == null) {
						  throw new DataException("Can not connect to Network HSQL Server if no host is specified");
					 }
					 url = "jdbc:hsqldb:hsql://"+host+":"+DataBaseUtilsHSQL.HSQL_DBPORT+"/"+dbname;
					 DataBaseUtilsHSQL.startHsqlServer(path,dbname);
					 break;
				}
				case DBTYPE_FIREB: {
					 classForName = "org.firebirdsql.jdbc.FBDriver";
					 url = "jdbc:firebirdsql:"+host+"/3050:"+path+"/"+dbname;
					 break;
				}
				case DBTYPE_DERBY_EMBED: {
					 classForName = "org.apache.derby.jdbc.EmbeddedDriver";
					 url = "jdbc:derby:"+dbname+";create=true";
					 break;
				}
				case DBTYPE_DERBY_NETWORK: {
					 System.setProperty("derby.drda.startNetworkServer","true");
					 classForName = "org.apache.derby.jdbc.EmbeddedDriver";
					 url = "jdbc:derby:"+dbname+";create=true";
					 break;
				}
				case DBTYPE_MSSQL: {
					 classForName = "com.microsoft.jdbc.sqlserver.SQLServerDriver";
					 url = "jdbc:microsoft:sqlserver://"+host+":1433";
					 break;
				}
				default: {
					 throw new DataException("TYPE ["+dbtype+"] has not been supported");
				}
		  }
		  Log.trace.out("DATABASE CONNECTION","DATABASE CONNECTION INIT ["+url+"]");
		  // TRY TO CREATE THE DATABASE ONLY IF IT IS THE LOCALHOST
		  /*try {
				if ((("localhost".equalsIgnoreCase(host) || InetAddress.getLocalHost().getHostAddress().equalsIgnoreCase(host)) &&
						  !DBConnection.getDataBaseUtils(url).createDatabase(path,p_dbname,username,password))){
					 throw new DataException("COULD NOT CREATE DATABASE AND DATABASE DID NOT EXIST");
				}
		  } catch (java.net.UnknownHostException uhe) {
				throw new DataException(uhe);
		  }*/
		  ThreadSchedule.add(this);
	 }

	 public ConnectionPool( int dbtype, String p_dbname, String p_username, String p_password)
	 throws DataException, SQLException {
		  this(dbtype,p_dbname,"",null,p_username,p_password);
	 }


	 public String getUrl() {
		  return url;
	 }

	 public synchronized DBConnection getConnection(boolean allow_close)
	 throws DataException, SQLException {
		  if (allow_close) {
				DBConStruct dbstruct = pool.get();
				if (dbstruct == null) {
					 System.out.println("Created DB Connection " + allow_close);
					 dbstruct = new DBConStruct(new DBConnection( this,classForName, dbname,host,url, path,username, password, allow_close));
					 pool.add(dbstruct);
				}
				return dbstruct.dbcon;
		  } else {
				return new DBConnection( null,classForName, dbname,host,url, path,username, password, allow_close);
		  }
	 }

	 public synchronized DBConnection getConnection()
	 throws DataException, SQLException {
		  return getConnection(true);
	 }

	 public void connectionAvailable(DBConnection dbcon) throws SQLException {
		  pool.get(dbcon).status_available = true;
	 }

	 public boolean keepAlive() {return true;}

	 public int sleepTime() {return 15000;}

	 public void process() throws Throwable {
		  int cropped = pool.crop(POOLSIZE);
		  pool.recycle();
		  Log.trace.out("CONNECTION POOL","POOL SIZE IS " + pool.size() + ". MAX POOL SIZE " + POOLSIZE + ". LAST RESYCLED " + cropped);
	 }

	 public void close() throws Throwable {
		  for (;!pool.close();Thread.sleep(1000));
	 }

	 private static class DBConStruct {
		  public DBConnection dbcon = null;
		  public boolean status_available = true;
		  public long lastRecycled = 0;

		  public DBConStruct(DBConnection dbcon) {
				this.dbcon = dbcon;
		  }
	 }

	 private class VectorDBConStruct {
		  private Vector<DBConStruct> store = new Vector<DBConStruct>();

		  public void add(DBConStruct dbcon) {
				store.add(dbcon);
		  }

		  public DBConStruct remove(int index) {
				return (DBConStruct) store.remove(index);
		  }

		  public boolean close() throws SQLException {
				boolean unavailable = false;
				for (int i = 0;i < size();i++) {
					 if (get(i).status_available) {
						  remove(i--).dbcon.getDBConnection().close();
					 } else {
						  unavailable= true;
					 }
				}
				return !unavailable;
		  }

		  public int size() {return store.size();}

		  public DBConStruct get(int index) {
				return (DBConStruct) store.get(index);
		  }

		  public DBConStruct get(DBConnection dbcon) {
				for (int i = 0;i < size();i++) {
					 if (get(i).dbcon == dbcon) {
						  return get(i);
					 }
				}
				return null;
		  }

		  /**
			* This will return the first available
			*/
		  public DBConStruct get() {
				synchronized (store) {
					 for (int i = 0;i < size();i++) {
						  if (get(i).status_available) {
								get(i).status_available = false;
								return get(i);
						  }
					 }
					 return null;
				}
		  }

		  public int crop(int poolsize) throws SQLException {
				int cnt = 0;
				synchronized (store) {
					 for (int i = 0;size() > poolsize && i < size();i++) {
						  if (get(i).status_available) {
								cnt++;
								remove(i--).dbcon.getDBConnection().close();
						  }
					 }
				}
				return cnt;
		  }

		  /**
			* Recycle all that is older than 60 seconds
			*/
		  public void recycle() throws SQLException {
				synchronized (store) {
					 for (int i = 0;i < size();i++) {
						  if (get(i).status_available && System.currentTimeMillis() - get(i).lastRecycled > 60000) {
								get(i).dbcon.getDBConnection().close();
								get(i).dbcon = get(i).dbcon.getNewInstance();
						  }
					 }
				}
		  }
	 }

}
